Webpack源码分析4--之 Webpack的灵魂强大的事件流Tapable(I)--异步 hook

January 18, 2020

同步的实现比较简单,但同步是满足不了我们很多业务场景,比方你需要你在某个 事件流程了需要有个后端接口请求,根据这种异步返回的结果决定下一个动作,实际上我们在真实的业务场景里,异步请求更多

AsyncSeries 异步串行

异步串行的意思是,需要执行一个 function 数组,这些 function 是有顺序的,现在某个方法里有 异步请求,下个 fn 需要等到 这个异步请求完成才执行下一个 fn ,来个具体例子

 const {
	AsyncSeriesHook
 } = require("../my-tapable");

 const hook = new AsyncSeriesHook(['name']);
 hook.tap('1',(name,callback) => {
     setTimeout(() => {
        console.log('Hello',name);
        callback()
     },1000)
    
 })
 hook.tap('2',(name,callback) => {
    setTimeout( () => {
        console.log('Wellocome',name);
        callback(null)
    },2000)
    
 })

hook.tap('3',(name,callback) => {
    console.log('Happy',name);
    callback()
})

hook.callAsync('chromedev',function(){
    console.log('finish',...arguments)
});

期待结果输出:

Hello hucheng
Wellocome hucheng
Happy hucheng
finish

可以看到我们需要的是 在 setTimeout 这种异步方法 执行回调,哪这个怎么实现的?,写个简单的代码实现,大家看下

class AsyncSeriesHook  {
    constructor(args) {
        this.taps = []
    }
    tap(name, fn) {
        this.taps.push({ name, fn })
    }
    // 仔细看代码的实现,这就是 JavaScript 的魅力所在
    callAsync(name, fn) {
        let i = 0        
        let nextFn = (error) => {
            if(error){return fn(error)}
            i++
            if(i >= this.taps.length){return fn()}
            this.taps[i].fn.apply(this,[name,nextFn])
           // 注意看这里,上面的nexFn,就放到了 调用的地方了  
         
        }
        this.taps[0].fn.apply(this,[name,nextFn])
    }
    /**
    * hook.tapAsync('1',(name,callback) => {
        setTimeout(() => {
            console.log('Hello',name);
            callback()
        },1000)
        
    })
    */
}

AsyncParallel 异步并行

异步并行的意思是,需要执行一个 function 数组,这些 function 并行执行,相互不影响,在 所有的 function 执行完成后,调用 callback, 也是一个常见的场景

class AsyncParallelHook extends Hook{
    callAsync(name, fn) {
       let remaining = this.taps.length;
      const callbackFn =  (error) =>{
            if(remaining <0) return;
            if(error){
                remaining = -1;
                return fn(error);
            }
            remaining--;
            if(remaining == 0){
                return fn()
            }
       }
       for(var i = 0; i < this.taps.length; i++) {
        this.taps[i].fn.apply(this, [name,callbackFn]);
       } 
    }
}

AsyncSeriesBailHook,AsyncSeriesWaterfallHokk,AsyncParallerBailHook 实现就都比较简单了,这里就不列举了,具体可以看 我 github 代码实现

总结

用2篇 梳理了 Tapable 的代码实现,Webpack 源码里面有各种 Tapable 提供的 hook 使用,实际上Tapable 是典型的事件流,而事件流基本是前端在处理复杂业务必须用到,这样才能让代码解藕,非常值得大家去好好的学习它,这还是单个的同步,异步 hook, 那把二者结合起来,功能更加强大,下篇就看看那 webpack resolve 模块是怎么灵活结合使用这 2 种 hook 的